/*!
Copyright (c) 2012
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function(context, baseLibId){

var baseLib = {

    /**
    @method: apply
    @param: obj object
    @param: prop object

    @result: object

    Given an object "obj", copy each property specified in "prop" to "obj".
    Any property that exists "obj" and also exists in "prop" will be overwritten by that property that exists in "prop".
    The resulting object will be returned by this function.

    Note: Use this function with caution. It does not check if the property intended to be overwritten is a native one or not; hence,
    an unintentional overwriting of some native property might cause problems. Only use this function if the properties that will
    be overwritten are indeed user-defined properties, and not system-defined ones (such as the example below).
    
    Example:
    var obj = {a : 1, b : 2};
    var prop = {a : 2, c : 3};
    var result = apply(obj, prop);
    console.log(result); //will output: {a : 2, b : 2, c : 3};
    */
    apply : function(obj, prop){

        var name;
        for(name in prop){
            obj[name] = prop[name];
        }
        
        return obj;
    }
   
    /**
    @method: applyIf
    @param: obj object
    @param: prop object

    @result: object

    Given an object "obj", copy each property specified in "prop" to "obj".
    Any property that exists "obj" and also exists in "prop" will NOT be overwritten by that property that exists in "prop".
    The resulting object will be returned by this function.

    Note: Use this function with caution. It does not check if the property intended to be overwritten is a native one or not; hence,
    an unintentional overwritting of some native property might cause problems. Only use this function if the properties that will
    be overwritten are indeed user-defined properties, and not system-defined ones (such as the example below).

    Example:
    var obj = {a : 1, b : 2};
    var prop = {a : 2, c : 3};
    var result = applyIf(obj, prop);
    console.log(result); //will output: {a : 1, b : 2, c : 3};
    */
   ,applyIf : function(obj, prop){

        var name;
        for(name in prop){
            
            if( obj[name] === undefined ){		
                obj[name] = prop[name];
            }
        
        }
        
        return obj;
        
    }
    
    /**
    @method: each
    @param: set array|object
    @param: callback function
    @param: scope object

    @result: undefined

    This function will traverse a given traversable ojbect "set" (which could be either an array or an object), calling the function "callback"
    for each item it encounters.
    The function "callback", for each traversal, will be invoked with the following arguments depending on the type of "set" supplied to this function.

    If "set" is an array, "callback" will be invoked with:

    index - the numeric index of the item in question.
    item - the item of the array in question
    set - the array in question.

    If "set" is an object, "callback" will be invoked with:
    name - the name of a property in the object in question.
    item - the value of a property in the object in question.
    set - the object in question.

    In either case, the traversal of "set" will be halted if "callback" will return a boolean FALSE value; otherwise, it will continue
    to traverse "set" upto the last item.

    The object "scope" will be the context in which callback will be invoked. It defaults to the global scope, which is the context object.

    Example:
    var set = ['a', 'b', 'c'];
    var callback = function(index, item, items){
        console.log(index + " " + item + " " + items.join(','));
    };

    each(set, callback);
    //above invocation will print in the console the following lines:
    //1 a a,b,c
    //2 b a,b,c
    //3 c a,b,c

    var set = {"prop1" : "a", "prop2" : "b", "prop3" : "c"};
    var callback = function(index, item, items){
        console.log(index + " " + item + " " + JSON.stringify(items));
    };

    each(set, callback);
    //above invocation will print in the console the following lines:
    //prop1 a {"prop1" : "a", "prop2" : "b", "prop3" : "c"}
    //prop2 b {"prop1" : "a", "prop2" : "b", "prop3" : "c"}
    //prop3 c {"prop1" : "a", "prop2" : "b", "prop3" : "c"}

    */
   ,each : function(set, callback, scope){

        scope = scope || context;
        var r = true;
        
        if( set instanceof Array ){
            var i, len;
            for(i = 0, len = set.length; i < len; i++){
                r = callback.apply(scope, [i, set[i], set]);
                if( r === false ){
                    break;
                }
            }
        }
        else{
            var key;
            for(key in set){
                r = callback.apply(scope, [key, set[key], set]);
                if( r === false ){
                    break;
                }			
            }
        }
        
    }
   
    /**
    @method: isString
    @param: v object

    @result: boolean
    */
   ,isString : function(v){

        if( v === null ){
            return false;
        }
        
        var toString = Object.prototype.toString;
        return toString.call(v) === '[object String]';
        
    }
    
    /**
    @method: isArray
    @param: v object

    @result: boolean
    */
   ,isArray : function(v){

        if ( v === null ){
            return false;
        }

        var toString = Object.prototype.toString;
        return toString.call(v) === '[object Array]';
        
    }
   
    /**
    @method: isScalar
    @param: v object

    @result: boolean

    A scalar value is a value which does not have any user-visible components -- such as numbers, strings, and booleans.
    An array or object or non-scalars, since they are in turn made of scalar values.
    */
   ,isScalar : function(v){

        if( v === null ){
            return true;
        }
        
        var toString = Object.prototype.toString;
        return !( this.isArray(v) || toString.call(v) === '[object Object]');
        
    }
   
    /**
    @method: isNonScalar
    @param: v object

    @result: boolean

    A nonscalar value is a value which does have user-visible components -- such as arrays and objects.
    They are user-visible components since one can access those components through some means made available by the language in question.
    For instance, invidual components of arrays can be accessed through indices arr[0], arr[1], and so one. The same can be said about objects.
    */
   ,isNonScalar : function(v){
        return this.isScalar(v) === false;
    }
    
    /**
    @method: hasCompatibleTypes
    @param: v1 object
    @param: v2 object

    @result: boolean

    Two values, v1 and v2 are said to be type-compatible if they are of the same type or they inherit, or v2 inherits from v1.
    Currently, however, this function only tests if v1 and v2 are of the same type; hence the second case will (that is, v2 inherits from v1) is 
    not checked and will return FALSE.
    */
   ,hasCompatibleTypes : function(v1, v2){

        var toString = Object.prototype.toString;
        return (toString.call(v1) === toString.call(v2) && v1.constructor === v2.constructor);
        
    }
   
    /**
    @method: hasCompatibleTypes
    @param: v1 object
    @param: v2 object
    @param: isStrict boolean
    @param: isInRelExp boolean

    @result: boolean

    */
   ,isEqual : function(v1, v2, isStrict, isInRelExp){

        isStrict = (isStrict === undefined ? true : isStrict);	
        isInRelExp = (isInRelExp === undefined ? false : isInRelExp);
        
        if( v1 === null || v2 === null ){
            //equality operations involving NULL should always evaluate to false
            if( isStrict ) {
                return false;
            }
            return v1 === v2;
        }

        //check if have compatible types
        if( !this.hasCompatibleTypes(v1, v2) ){
            return false;
        }
        
        var v1IsScalar = this.isScalar(v1)
            ,v2IsScalar = this.isScalar(v2);
            
        //both are scalar
        if( v1IsScalar && v2IsScalar ){
        
            if( this.isString(v1) && this.isString(v2) ){
                return v1.toUpperCase() === v2.toUpperCase();
            }
            
            //relation "equality" comparison treats 1 and "1" as equal. so convert everything to string.
            if( isInRelExp ){
                v1 = v1.toString();
                v2 = v2.toString();
            }
            
            return v1 === v2;
        }
        
        //both are non-scalar
        if( !v1IsScalar && !v2IsScalar ){
            
            if( this.count(v1) !== this.count(v2) ){
                    return false;
            }
            
            var r = true;
            
            this.each(v1, function(idx, item){
            
                if( v2[idx] === undefined ){
                    r = false;
                    return false; //break the each function
                }
                
                if( false === this.isEqual(item, v2[idx], isStrict, isInRelExp) ){
                    r = false;
                    return false; //break the each function
                }

            }, this);
            
            return r;
        }
        
        return false;
        
    }
   
    /**
    @method: count
    @param: val object

    @result: boolean

    */
   ,count : function(val){

        if( val.length !== undefined ){
            return val.length;
        }
            
        if( this.isScalar(val) ){
            throw new Error("Cannot determine cardinality for scalar values.");
        }
        
        var cnt = 0;
        this.each(val, function(){
            cnt++;
        }, this);
        
        return cnt;

    }

    /**
    @method: isEmpty
    @param: val object

    @result: boolean

    */
   ,isEmpty : function(val){

        var isEmpty = true;
        
        if( this.isNonScalar(val) ){
            this.each(val, function(){
                isEmpty = false;
                return false;
            });		
        }
        else{
            if( this.isString(val) && val !== '' ){
                isEmpty = false;
            }
            else if ( !isNaN(val) && val !== 0 ){
                isEmpty = false;
            }		
        }
        
        return isEmpty;
    }
       
    /**
    @method: inArray
    @param: row object
    @param: row array | object
    @param: isStrict boolean
    @param: isInRelExp boolean

    @result: boolean

    */
   ,inArray : function(row, rows, isStrict, isInRelExp){

        row = this.isScalar(row) ? [row] : row;
        
        var result = false
            ,givenRowCount    = this.count(row);

        this.each(rows, function(idx, _row){
            
            result = false;
            
            _row = this.isScalar(_row) ? [_row] : _row;
            
            var _rowCount = this.count(_row);

            if( givenRowCount === _rowCount ){
                
                if( givenRowCount === 0 ){
                    result = true;
                    return false; // break the loop
                }
                
                var _r = true;
                this.each(_row, function(field, val){				
                    _r = _r && this.isEqual(val, row[field], isStrict, isInRelExp);
                    if( ! _r  ){
                        return false; //break the loop
                    }
                }, this);
                
                if(  _r ) {
                    result = true;
                    return false;  //break the loop
                }

                
            }
            
        }, this);
        
        return result;
        
    }
   
    /**
    @method: sortFn
    @param: fields object

    @result: function

    */
   ,sortFn : function(fields){

        return function(row1, row2){
        
            var _sFn = function(row1, row2, orderItem){
            
                var  v1
                      ,v2
                      ,sortExpr = orderItem.expr;

                if( sortExpr ){
                
                    var evaluator	= sortExpr.evaluator
                         ,node			= sortExpr.node
                         ,scope			= sortExpr.scope
                         ,fields			= sortExpr.fields;

                    v1 = evaluator.apply(scope, [node, row1, fields, [], false]);
                    v2 = evaluator.apply(scope, [node, row2, fields, [], false]);
                    
                }
                else{
                    var map = orderItem.mapping;
                    v1 = row1[map];
                    v2 = row2[map];
                }

                if( orderItem.nocase ){
                    v1 = v1.toString().toUpperCase();
                    v2 = v2.toString().toUpperCase();
                }

                if( v1 === v2 ){
                    return 0;
                }

                return ( orderItem.direction === 'ASC' ) ?
                    (v1 < v2 ? -1 : 1) :
                    (v1 > v2 ? -1 : 1);
                
            };
            
            var sf = fields
                 ,sfLen = fields.length
                 ,result = 0
                 ,i = 0;
            
            while( result === 0 && i < sfLen ){		
                result = _sFn(row1, row2, sf[i]);			
                i++;
            }

            return result;
        
        };

    }

    /* @license
     * Simple JavaScript Inheritance
     * By John Resig http://ejohn.org/
     * MIT Licensed.
     */
    // Inspired by base2 and Prototype
   ,extend : (function(){
        
        var        
              initializing = false
             ,fnTest      = /\b_super\b/
             ,extend     = function(prop){
         
                   var Parent = typeof this === 'function' ? this : function(){}
            
                         ,_super = Parent.prototype
                
                         ,method = function(name, fn){
                    
                            return function() {
                            
                                var tmp = this._super;
                               
                                // Add a new ._super() method that is the same method
                                // but on the super-class
                                this._super = _super[name];
                            
                                // The method only need to be bound temporarily, so we
                                // remove it when we are done executing
                                var ret = fn.apply(this, arguments);        
                                this._super = tmp;
                                
                                return ret;
                                
                            };
                            
                         }
                
                         ,propVal, val, name;
            
                    // Instantiate a base class (but only create the instance, don't run the init constructor)
                    initializing = true;
                    var _prototype = new Parent();
                    initializing = false;
                
                    // Copy the properties over onto the new prototype
                    for (name in prop) {

                        propVal = prop[name];
                        
                        // Check if overwriting an existing function
                        val = typeof propVal === "function" &&  //check if current property is a function
                                 typeof _super[name] === "function" &&  //check if the parent's property is a function
                                 fnTest.test(propVal) //check if the current property metioned the "_super" keyword.
                                 
                                 //if all those 3 conditions are satisfied, create a special method which will be invoked so
                                 //that the _super method is invoked too
                                 ? method(name, propVal)
                                 
                                 //else, just a simple override.
                                 : propVal;

                        _prototype[name] = val;
                    }

                    // The dummy class constructor
                    function Class() {
                    
                        // All construction is actually done in the init method
                        if ( !initializing && this.init ){
                            this.init.apply(this, arguments);
                        }
                        
                    }

                    // Populate our constructed prototype object
                    Class.prototype = _prototype;
                    
                    // Enforce the constructor to be what we expect
                    Class.prototype.constructor = Class;

                    // And make this class extendable
                    Class.extend = extend;
                    
                    return Class;
            
                };
         
         return extend;
         
   }())
   
};

context[baseLibId] = baseLib;

if( typeof define === 'function' && define.amd ){
    define(baseLibId, [], function(){ return baseLib; });
}

}(this, 're'));